仓颉增量引擎相关概念解析.md•9 kB
# 增量引擎相关概念解析
> 本文档解析增量引擎中的核心概念,包括 Scope 创建/复用机制、宏展开原理、recache 机制、根装配与冻结窗口等。
**基于**: Cangjie/KoalaRuntime 实现
**核心文件**: `runtime/src/core/StateManagerImpl.cj`, `runtime/src/core/ScopeImpl.cj`, `runtime/src/memoize/visitor.cj`
---
## 目录
1. [Scope 何时创建/复用](#scope-何时创建复用)
2. [宏展开:怎样把普通函数变成"可增量"](#宏展开怎样把普通函数变成可增量)
3. [recache 做了什么](#recache-做了什么)
4. [根装配与冻结窗口](#根装配与冻结窗口)
5. [以创建为例子查看宏展开](#以创建为例子查看宏展开)
6. [某个状态变化(以组件 B 的依赖状态为例)](#某个状态变化以组件-b-的依赖状态为例)
7. [刷新后再次变化(重复增量)](#刷新后再次变化重复增量)
8. [依赖登记要点(读时注册)](#依赖登记要点读时注册)
9. [小结](#小结)
---
## Scope 何时创建/复用
`getMemoScope` 使用“父作用域 + 调用点哈希 id + 参数计数”来定位/创建子作用域:
```328:344:/home/gloria/Cangjie/incremental_runtime/runtime/src/core/StateManagerImpl.cj
public func getMemoScope<Value>(id: Hashscopeid, paramCount: Int64): MemoScope<Value> {
getMemoScope(id, paramCount, None, None, None, false, None)
}
...
let new_scope = scope.getChildScope<Value>(id, paramCount, create, compute, cleanup, once, reuseKey)
```
在父作用域中按 `id` 扫描兄弟链找到可复用的子作用域;未命中则创建并接入链表/树:
```292:345:/home/gloria/Cangjie/incremental_runtime/runtime/src/core/ScopeImpl.cj
func getChildScope<V>(...): ScopeImpl<V> {
...
while (let Some(child) <- option) {
...
if (child.id == id) {
this.detachChildScopes(child)
this.incremental = child
return (child as ScopeImpl<V>).getOrThrow()
}
}
...
let scope = ScopeImpl<V>(id, paramCount, compute, cleanup, reuseKey)
scope.manager = this.manager
...
this.incremental = scope
return scope
}
```
### 宏展开:怎样把普通函数变成“可增量”
通过 `Memo`/`MemoIntrinsic` 宏,编译期对函数进行注入:
- 自动添加参数 `(__memo_key: Hashscopeid, manager: StateManagerImpl)`;
- 函数体首部获取/创建 `__memo_scope = manager.getMemoScope(...)`;
- 将原始形参包为 `State` 参数(`param(index, value)`);
- 首次/需要重算时走 `compute → recache`,否则直接 `getCached()`;
- 在 return 路径与内联 lambda 中自动插入 `__memo_scope.recache(...)`。
关键注入片段如下:
```173:187:/home/gloria/Cangjie/incremental_runtime/runtime/src/memoize/visitor.cj
public override func visit(funcDecl: FuncDecl) {
...
funcDecl.funcParams.add(FuncParam(quote(__memo_key: Hashscopeid)), at: 0)
funcDecl.funcParams.add(FuncParam(quote(manager: StateManagerImpl)), at: 1)
...
funcDecl.block.nodes.add(
VarDecl(
quote(let __memo_scope = manager.getMemoScope < $(funcType) >(__memo_key + $(CallsiteKey.getCallsiteKey(funcDecl.beginPos.dump().toString())), $(memoizedParams.size)))
))
...
}
```
### recache 做了什么
- 恢复 `manager.currentScope` 回到进入计算前的作用域;
- 更新当前作用域的缓存值、`myComputed`、`myModified`、`recomputeNeeded`;
- 断开已失效的增量子树 `detachChildScopes(None)`;
- 根据本次子节点增量更新统计,驱动父节点的 `incrementalUpdateDone`;
- 读取缓存时会将依赖登记到本作用域的 `Dependencies`。
```416:438:/home/gloria/Cangjie/incremental_runtime/runtime/src/core/ScopeImpl.cj
public func recache(newValue: Value): Value {
if (!this.isDisposed()) {
if(let Some(manager) <- this.manager) {
manager.currentScope = this.scopeInternal
}
}
let oldValue = this.myValue
this.myValue = newValue
this.myModified = this.myComputed && !equalValues(newValue, oldValue.getOrThrow())
this.myComputed = true
this.recomputeNeeded = false
this.detachChildScopes(None)
this.parent?.increment(
if (this.nodeAttached.isSome()) { 1 } else { this.nodeCount },
false
)
this.nodeAttached?.incrementalUpdateDone(this.parent?.nodeRef ?? None)
return this.getCached()
}
```
### 根装配与冻结窗口
根装配通过 `memoRoot → updatableNode` 完成,并在“冻结窗口”内执行 `update`(避免构建阶段推进快照):
```29:33:/home/gloria/Cangjie/incremental_runtime/runtime/src/core/StateManagerImpl.cj
public func memoRoot<Node>(node: Node, update: (StateManagerImpl) -> Unit): ComputableState<Node> where Node <: IncrementalNode {
let manager = GlobalStateManager.instance()
manager.updatableNode(node, { => manager.runWithFrozen(update)}, None)
}
```
```165:183:/home/gloria/Cangjie/incremental_runtime/runtime/src/core/StateManagerImpl.cj
public func updatableNode<Node>(node: Node, update: () -> Unit, cleanup: ?() -> Unit): ComputableState<Node> where Node <: IncrementalNode {
...
let scope = ScopeImpl<Node>(
None,
2,
{ => update(); node },
{_ => cleanup?()},
None
)
scope.manager = this
scope.nodeAttached = node
scope.nodeRef = node
...
return scope
}
```
```55:60:/home/gloria/Cangjie/incremental_runtime/runtime/src/core/StateManagerImpl.cj
func runWithFrozen(runnable: (StateManagerImpl) -> Unit): Unit {
let old = frozen
frozen = true
runnable(this)
frozen = old
}
```
### 以创建为例子查看宏展开
冻结窗口内执行 `update`,组件 A、B 的被注入函数体:
- 注入 `__memo_key` 与 `manager`;
- 首部 `__memo_scope = manager.getMemoScope(...)`;
- 形参通过 `param()` 转为 `State`,读取将登记依赖;
- 首次进入 `scope.isUnchanged() == false`,走 `compute → recache` 完成缓存与节点接入。
示例(多参 `memoN` 的公共形态):
```48:56:/home/gloria/Cangjie/incremental_runtime/runtime/src/core/memo.cj
let scope = manager.getMemoScope<Value>(_new_id, 2, None, None, None, false, None)
let s1 = scope.param(0, p1)
let s2 = scope.param(1, p2)
if (scope.isUnchanged()) { scope.getCached() } else { scope.recache(compute(s1, s2)) }
```
### 某个状态变化(以组件 B 的依赖状态为例)
1) 写入状态:
```104:118:/home/gloria/Cangjie/incremental_runtime/runtime/src/core/StateImpl.cj
public func setValue(value: Value) {
...
this.updated = false
if (let Some(manager) <- this.manager) {
manager.updateNeeded = true
} else {
this.updateStateSnapshot()
}
}
```
2) 触发快照推进:
```37:40:/home/gloria/Cangjie/incremental_runtime/runtime/src/core/GlobalStateManager.cj
public func updateStateManager(...): Unit { manager.updateSnapshot() }
```
3) 在 `updateSnapshot` 中:
- 先 diff 所有 `createdStates`;
- 消费 `dirtyScopes`,对受影响作用域 `scope.isModified()`,必要时重算;
```134:163:/home/gloria/Cangjie/incremental_runtime/runtime/src/core/StateManagerImpl.cj
public func updateSnapshot(): UInt64 { ... }
```
4) 脏标记如何产生:参数/作用域调用 `invalidate()`,自下而上标记 `recomputeNeeded = true`,到达顶层且有依赖时加入 `dirtyScopes`:
```240:263:/home/gloria/Cangjie/incremental_runtime/runtime/src/core/ScopeImpl.cj
public func invalidate(): Unit { ... manager.addDirtyScope(scope) ... }
```
### 刷新后再次变化(重复增量)
后续任意一次状态变化,重复“标记需要更新 → updateSnapshot → 仅重算脏作用域”。未变化子树由 `IncrementalNode.incrementalUpdateSkip(count)` 快速跳过:
```155:173:/home/gloria/Cangjie/incremental_runtime/runtime/src/core/IncrementalNode.cj
func incrementalUpdateSkip(count: UInt32) { ... }
```
### 依赖登记要点(读时注册)
- 作用域缓存读取:
```409:414:/home/gloria/Cangjie/incremental_runtime/runtime/src/core/ScopeImpl.cj
public func getCached(): Value { this.dependencies?.onUpdate(this.myModified); this.myValue.getOrThrow() }
```
- State 读取:
```80:84:/home/gloria/Cangjie/incremental_runtime/runtime/src/core/StateImpl.cj
private func onAccess(): Unit { ... this.dependencies?.register(dep) }
```
- 参数读取:
```45:50:/home/gloria/Cangjie/incremental_runtime/runtime/src/core/ParameterImpl.cj
public func getValue(): Value { ... this.dependencies?.register(dep) ; return this.value }
```
### 小结
- 宏把普通函数转为“有作用域、可追踪依赖、可缓存”的增量函数;
- 作用域以调用点为键在父-子关系中创建/复用,`recache` 负责值更新、子树维护与节点接入;
- 状态写入只置位 `updateNeeded`,实际比较与作用域重算发生在 `updateSnapshot()`;
- 未变更部分通过节点级“跳过”实现高效增量,整体更新最小化。